/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.cx1.service.impl;

import com.javadocmd.simplelatlng.LatLng;
import com.javadocmd.simplelatlng.LatLngTool;
import com.javadocmd.simplelatlng.util.LengthUnit;
import com.rits.cloning.Cloner;
import org.cx1.service.impl.deprecated.PlaceSearchService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.en.EnglishAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;
import org.cx1.model.Amenity;
import org.cx1.model.FurnishType;
import org.cx1.model.ds.MockDS;
import org.cx1.model.Place;
import org.cx1.model.Listing;
import org.cx1.model.PropertyType;
import org.cx1.model.StatusType;
import org.cx1.model.utils.KPlaceTypeIDs;
import org.cx1.model.utils.ListingComparator;
import org.cx1.model.utils.ListingFilter;
import org.cx1.model.utils.PlaceMarker;
import org.cx1.model.utils.RateUtils;
import org.cx1.model.utils.SearchFilter;
import org.cx1.model.utils.SearchOption;
import org.cx1.model.utils.SearchResult;
import org.cx1.model.utils.ServiceResponse;
import org.cx1.service.api.IListingService;
import org.cx1.service.api.IPlaceService;
import org.cx1.service.api.ISearchService;

/**
 *
 * @author ECH
 */
public class MSearchService implements ISearchService {
    
    private IListingService listingService = new MListingService();
    private IPlaceService placeService = new MPlaceService();
    
    private Cloner cloner = new Cloner();
    
    @Override
    public ServiceResponse<List<Listing>> searchListings(SearchOption searchOption) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> filtered = new ArrayList<Listing>();
        List<Listing> listings = null;            
        try {   
            //CX: get all
            ServiceResponse<List<Listing>> sr1 = listingService.readAllPosted(0, 0, true);
            if (sr1.isSuccessful() && sr1.getResponse() != null) {
                listings = sr1.getResponse();
            }               
            //CX: filter
            ServiceResponse<List<Listing>> sr2 = this.filterListings(listings, searchOption.getPlaces(), searchOption.getPropertyTypes(), searchOption.getFurnishTypes(), searchOption.getStatusTypes(), searchOption.getAmenities(), searchOption.getListingFilter());
            if (sr2.isSuccessful() && sr2.getResponse() != null) {
                filtered.addAll(sr2.getResponse());
            }             
            
            //CX: page
            List<Listing> paged = new ArrayList<Listing>();
            int ixStart = searchOption.getPage() == 1 ? 0 : ((searchOption.getPage() * searchOption.getItemsPerPage()) - searchOption.getItemsPerPage());
            int ixEnd = (searchOption.getPage() * searchOption.getItemsPerPage());
            //int ixEnd = (searchOption.getPage() * searchOption.getItemsPerPage()) - 1;
            
            if (filtered.size() > ixEnd ) {
                paged.addAll(filtered.subList(ixStart, ixEnd));                
            }            
            else {
                paged.addAll(filtered.subList(ixStart, filtered.size()-1));                   
            }            
            
            //CX: sort by date
            Collections.sort(paged, new ListingComparator());
            
            response.setResponse(paged);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
    
    @Override
    public ServiceResponse<SearchResult> searchListings2(SearchOption searchOption) {
        ServiceResponse<SearchResult> response = new ServiceResponse<SearchResult>();
        SearchResult searchResult = new SearchResult();
        List<Listing> filtered = new ArrayList<Listing>();
        List<Listing> listings = new ArrayList<Listing>();
        List<Listing> pagedListings = new ArrayList<Listing>();
        
        int totalListings = 0;
        boolean hasNext = false;
        boolean hasPrev = false;
        
        try {   
            //CX: get all
            //ServiceResponse<List<Listing>> sr1 = listingService.readAllPosted(0, 0, true);
            ServiceResponse<List<Listing>> sr1 = listingService.readAllPosted(0, 0, true, searchOption.getLeaseTerm());
            if (sr1.isSuccessful() && sr1.getResponse() != null) {
                listings = sr1.getResponse();
            }               
            //CX: filter
            ServiceResponse<List<Listing>> sr2 = this.filterListings(listings, searchOption.getPlaces(), searchOption.getPropertyTypes(), searchOption.getFurnishTypes(), searchOption.getStatusTypes(), searchOption.getAmenities(), searchOption.getListingFilter());
            if (sr2.isSuccessful() && sr2.getResponse() != null) {
                filtered.addAll(sr2.getResponse());
                totalListings = sr2.getResponse().size();
            }             
            
            //CX: sort by date
            Collections.sort(filtered, new ListingComparator());
            
            //CX: page
            int ixStart = searchOption.getPage() == 1 ? 0 : ((searchOption.getPage() * searchOption.getItemsPerPage()) - searchOption.getItemsPerPage());
            int ixEnd = (searchOption.getPage() * searchOption.getItemsPerPage());
            //int ixEnd = (searchOption.getPage() * searchOption.getItemsPerPage()) - 1;
            
            if (filtered.size() > ixEnd ) {
                pagedListings.addAll(filtered.subList(ixStart, ixEnd));                
            }            
            else {
                pagedListings.addAll(filtered.subList(ixStart, filtered.size()));                   
            }            
            
            //CX: check has next            
            if (totalListings > (searchOption.getPage() * searchOption.getItemsPerPage())) {
                hasNext = true;
            }
            
            //CX: check has prev
            if (searchOption.getPage() > 1 && searchOption.getPage() != 1) {
                hasPrev = true;
            }
            
            searchResult.setListings(this.trimListingsData(pagedListings));
            searchResult.setTotalListings(totalListings);
            searchResult.setHasNext(hasNext);
            searchResult.setHasPrev(hasPrev);
            
            response.setResponse(searchResult);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
            
    @Override
    public ServiceResponse<SearchResult> searchListings3(SearchOption searchOption) {
        ServiceResponse<SearchResult> response = new ServiceResponse<SearchResult>();
        SearchResult searchResult = new SearchResult();
        List<Listing> filtered = new ArrayList<Listing>();
        List<Listing> listings = new ArrayList<Listing>();
        List<Listing> pagedListings = new ArrayList<Listing>();
        
        int totalListings = 0;
        boolean hasNext = false;
        boolean hasPrev = false;
        
        try {   
            //CX: get all
            ServiceResponse<List<Listing>> sr1 = listingService.readAllPosted(0, 0, true, searchOption.getLeaseTerm());
            if (sr1.isSuccessful() && sr1.getResponse() != null) {
                listings = sr1.getResponse();
            }      
            
            //CX: filter
            ServiceResponse<List<Listing>> sr2 = this.filterListings(listings, searchOption.getPlaces(), searchOption.getPropertyTypes(), searchOption.getFurnishTypes(), searchOption.getStatusTypes(), searchOption.getAmenities(), searchOption.getListingFilter());
            if (sr2.isSuccessful() && sr2.getResponse() != null) {
                filtered.addAll(sr2.getResponse());
                totalListings = sr2.getResponse().size();
            }             
            
            //CX: sort by date
            Collections.sort(filtered, new ListingComparator());     
            
            //CX: page
            int ixStart = searchOption.getPage() == 1 ? 0 : ((searchOption.getPage() * searchOption.getItemsPerPage()) - searchOption.getItemsPerPage());
            int ixEnd = (searchOption.getPage() * searchOption.getItemsPerPage());
            //int ixEnd = (searchOption.getPage() * searchOption.getItemsPerPage()) - 1;
            
            if (filtered.size() > ixEnd ) {
                pagedListings.addAll(filtered.subList(ixStart, ixEnd));                
            }            
            else {
                pagedListings.addAll(filtered.subList(ixStart, filtered.size()));                   
            }            
            
            //CX: check has next            
            if (totalListings > (searchOption.getPage() * searchOption.getItemsPerPage())) {
                hasNext = true;
            }
            
            //CX: check has prev
            if (searchOption.getPage() > 1 && searchOption.getPage() != 1) {
                hasPrev = true;
            }
            
            //CX: increment impressions
            if (pagedListings != null && pagedListings.size() > 0) {
                for (Listing pagedListing : pagedListings) {
                    ServiceResponse<Boolean> response1 = listingService.incrementImpressions(pagedListing.getId());
                }
            }
            
            searchResult.setListings(this.trimListingsData(pagedListings));
            searchResult.setTotalListings(totalListings);
            searchResult.setHasNext(hasNext);
            searchResult.setHasPrev(hasPrev);
            
            response.setResponse(searchResult);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response;
    }
    
    
    @Override
    public ServiceResponse<SearchResult> searchListingsGeo(SearchOption searchOption) {
        ServiceResponse<SearchResult> response = new ServiceResponse<SearchResult>();
        SearchResult searchResult = new SearchResult();
        
        List<Listing> filtered = new ArrayList<Listing>();
        List<Listing> listings = new ArrayList<Listing>();
        List<Listing> pagedListings = new ArrayList<Listing>();
        
        boolean hasNext = false;
        boolean hasPrev = false;
        
        try {   
            //CX: get all
            ServiceResponse<List<Listing>> sr1 = listingService.readAllPosted(0, 0, true, searchOption.getLeaseTerm());
            if (sr1.isSuccessful() && sr1.getResponse() != null) {
                listings = sr1.getResponse();
            }               
            
            //CX: filter by geo
            geoFilterListings(listings, searchOption.getCenterPoint(), searchOption.getDistanceKM());
                        
            //CX: filter
            ServiceResponse<List<Listing>> sr2 = this.filterListings(listings, searchOption.getPlaces(), searchOption.getPropertyTypes(), searchOption.getFurnishTypes(), searchOption.getStatusTypes(), searchOption.getAmenities(), searchOption.getListingFilter());
            if (sr2.isSuccessful() && sr2.getResponse() != null) {
                filtered.addAll(sr2.getResponse());
            }            
                        
            //CX: page
            int ixStart = searchOption.getPage() == 1 ? 0 : ((searchOption.getPage() * searchOption.getItemsPerPage()) - searchOption.getItemsPerPage());
            int ixEnd = (searchOption.getPage() * searchOption.getItemsPerPage());
            
            if (filtered.size() > ixEnd ) {
                pagedListings.addAll(filtered.subList(ixStart, ixEnd));                
            }            
            else {
                pagedListings.addAll(filtered.subList(ixStart, filtered.size()));                   
            }  
            
            searchResult.setListings(pagedListings);       
            searchResult.setHasNext(hasNext);
            searchResult.setHasPrev(hasPrev);
            
            response.setResponse(searchResult);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
    
    @Override
    public ServiceResponse<SearchResult> searchPlacesGeo(SearchOption searchOption) {
        ServiceResponse<SearchResult> response = new ServiceResponse<SearchResult>();
        SearchResult searchResult = new SearchResult();
        
        List<Listing> filtered = new ArrayList<Listing>();
        List<Listing> listings = new ArrayList<Listing>();
        List<Listing> pagedListings = new ArrayList<Listing>();
        
        int totalListings = 0;
        boolean hasNext = false;
        boolean hasPrev = false;
        
        try {   
            //CX: get all
            //ServiceResponse<List<Listing>> sr1 = listingService.readAllPosted(0, 0, true);
            ServiceResponse<List<Listing>> sr1 = listingService.readAllPosted(0, 0, true, searchOption.getLeaseTerm());
            if (sr1.isSuccessful() && sr1.getResponse() != null) {
                listings = sr1.getResponse();
            }               
            
            //CX: filter by geo
            geoFilterListings(listings, searchOption.getCenterPoint(), 3.0);
                        
            //CX: filter
            ServiceResponse<List<Listing>> sr2 = this.filterListings(listings, searchOption.getPlaces(), searchOption.getPropertyTypes(), searchOption.getFurnishTypes(), searchOption.getStatusTypes(), searchOption.getAmenities(), searchOption.getListingFilter());
            if (sr2.isSuccessful() && sr2.getResponse() != null) {
                filtered.addAll(sr2.getResponse());
                totalListings = sr2.getResponse().size();
            }            
                        
            pagedListings.addAll(filtered);
            
            List<Place> placeList = new ArrayList<Place>();
            Set<Place> placeSet = new HashSet<Place>();
            
            List<PlaceMarker> placeMarkers = new ArrayList<PlaceMarker>();
            
            //Map<String, Place> placeMap = new HashMap<String, Place>();            
            if (filtered != null && filtered.size() > 0) {
                for (Listing listing : listings) {
                    Place struct = getListingStruct(listing);                                        
                    if (struct != null) {
                        placeSet.add(struct); 
                    }   
                }
                
                if (placeSet.size() > 0) {
                    for (Place place : placeSet) {
                        for (Listing listing : listings) {                        
                            Place struct = getListingStruct(listing); 
                            if (struct != null && StringUtils.equalsIgnoreCase(place.getId(), struct.getId())) {
                                if (place.getListings() == null) {
                                    place.setListings( new ArrayList() );
                                }
                                place.getListings().add(listing);
                            }
                        }
                    }               
                    
                    for (Place place : placeSet) {
                        //List<Listing> placeListings = cloneListings(place.getListings());
                        //List<Listing> minifyListings = minifyListings(listings);
                        
                        ServiceResponse<Place> sr3 = placeService.readShallow(place.getId());
                        
                        if (sr3.isSuccessful() && sr3.getResponse() != null) {
                            PlaceMarker placeMarker = new PlaceMarker();
                            placeMarker.setPlace(sr3.getResponse());
                            //placeMarker.getListings().addAll(minifyListings);
                            placeMarkers.add(placeMarker);                            
                        }                        
                    }                    
                }
            }
            
            //placeList.addAll(placeSet);
            
            //searchResult.setPlaces(placeList);
            //searchResult.setListings(pagedListings);
            searchResult.setPlaceMarkers(placeMarkers);            
            searchResult.setTotalListings(totalListings);
            searchResult.setHasNext(hasNext);
            searchResult.setHasPrev(hasPrev);
            
            response.setResponse(searchResult);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
        
    private List<Listing> minifyListings(List<Listing> listings) {
        List<Listing> minifyList = new ArrayList<Listing>(0);
        for (Listing record : listings) {
            Listing minListing = new Listing();
            minListing.setId(record.getId());
           // minListing.setTitle(record.getTitle());
            //minListing.setPrimaryPhoto(record.getPrimaryPhoto());
            //minListing.setSqArea(record.getSqArea());
            //minListing.setMonthRate(record.getMonthRate());
            //minListing.setNumBedrooms(record.getNumBedrooms());
            
            minifyList.add(minListing);
        }
        return minifyList;
    }
    
    private Place getListingStruct(Listing listing) {
        Place struct = null;
        if (listing.getPlaces() != null && listing.getPlaces().size() > 0) {
            for (Place place : listing.getPlaces()) {
                if (StringUtils.equals(place.getPlaceTypeID(), KPlaceTypeIDs.BUILDING) || 
                    StringUtils.equals(place.getPlaceTypeID(), KPlaceTypeIDs.VILLAGE) ) {
                    struct = place;
                    break;
                }
            }
        }
        return struct;
    }
    
    @Override
    public ServiceResponse<List<Listing>> filterListings(List<Listing> listings, List<Place> places, List<PropertyType> propertyTypes, List<FurnishType> furnishTypes, List<StatusType> statusTypes, List<Amenity> amenities, ListingFilter listingFilter) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> filtered = new ArrayList<Listing>();            
        try {   
            ServiceResponse<List<Listing>> sr1 = this.filterByPlace(listings, places);
            if (sr1.isSuccessful() && sr1.getResponse() != null) {
                listings.clear();
                listings.addAll(sr1.getResponse());
            }            
            
            ServiceResponse<List<Listing>> sr2 = this.filterByPropertyType(listings, propertyTypes);
            if (sr2.isSuccessful() && sr2.getResponse() != null) {
                listings.clear();
                listings.addAll(sr2.getResponse());
            }            
            
            ServiceResponse<List<Listing>> sr7 = this.filterByFurnishType(listings, furnishTypes);
            if (sr7.isSuccessful() && sr7.getResponse() != null) {
                listings.clear();
                listings.addAll(sr7.getResponse());
            } 
            
            ServiceResponse<List<Listing>> sr4 = this.filterByStatusType(listings, statusTypes);
            if (sr4.isSuccessful() && sr4.getResponse() != null) {
                listings.clear();
                listings.addAll(sr4.getResponse());
            } 
            
            ServiceResponse<List<Listing>> sr5 = this.filterByAmenity(listings, amenities);
            if (sr5.isSuccessful() && sr5.getResponse() != null) {
                listings.clear();
                listings.addAll(sr5.getResponse());
            } 
                        
            ServiceResponse<List<Listing>> sr3 = this.filterByListingAttributes(listings, listingFilter);
            if (sr3.isSuccessful() && sr3.getResponse() != null) {
                listings.clear();
                listings.addAll(sr3.getResponse());
            }                    
            
            if (listings != null && listings.size() > 0) {
                filtered.addAll(listings);                
            }
            response.setResponse(filtered);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
    
    @Override
    public ServiceResponse<List<Listing>> filterByPlace(List<Listing> listings, List<Place> places) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> filtered = new ArrayList<Listing>();
        try {            
            if (listings != null && listings.size() > 0) {
                if (places != null && places.size() > 0) {
                    
                    for (Listing listing : listings) {
                        for (Place place : places) {
                            if (this.listingHasPlace(listing, place)) {
                                filtered.add(listing);
                            }
                        }
                    }
                }
                else {
                    filtered.addAll(listings);
                }
            }
            response.setResponse(filtered);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
    
    @Override
    public ServiceResponse<List<Listing>> filterByPropertyType(List<Listing> listings, List<PropertyType> propertyTypes) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> filtered = new ArrayList<Listing>();
        try {            
            if (listings != null && listings.size() > 0) {
                if (propertyTypes != null && propertyTypes.size() > 0) {
                    
                    for (Listing listing : listings) {
                        for (PropertyType prop : propertyTypes) {
                            if (listing.getPropertyType() != null && 
                                StringUtils.equals(listing.getPropertyType().getId(), prop.getId())) {
                                filtered.add(listing);
                            }
                        }
                    }
                }
                else {
                    filtered.addAll(listings);
                }
            }
            response.setResponse(filtered);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
        
    @Override
    public ServiceResponse<List<Listing>> filterByFurnishType(List<Listing> listings, List<FurnishType> furnishTypes) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> filtered = new ArrayList<Listing>();
        try {            
            if (listings != null && listings.size() > 0) {
                if (furnishTypes != null && furnishTypes.size() > 0) {
                    
                    for (Listing listing : listings) {
                        for (FurnishType prop : furnishTypes) {
                            if (listing.getFurnishType()!= null && 
                                StringUtils.equals(listing.getFurnishType().getId(), prop.getId())) {
                                filtered.add(listing);
                            }
                        }
                    }
                }
                else {
                    filtered.addAll(listings);
                }
            }
            response.setResponse(filtered);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
        
    @Override
    public ServiceResponse<List<Listing>> filterByStatusType(List<Listing> listings, List<StatusType> statusTypes) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> filtered = new ArrayList<Listing>();
        try {            
            if (listings != null && listings.size() > 0) {
                if (statusTypes != null && statusTypes.size() > 0) {
                    
                    for (Listing listing : listings) {
                        for (StatusType prop : statusTypes) {
                            if (listing.getStatusType() != null && 
                                StringUtils.equals(listing.getStatusType().getId(), prop.getId())) {
                                filtered.add(listing);
                            }
                        }
                    }
                }
                else {
                    filtered.addAll(listings);
                }
            }
            response.setResponse(filtered);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
    
    @Override
    public ServiceResponse<List<Listing>> filterByAmenity(List<Listing> listings, List<Amenity> amenities) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        Set<Listing> filteredSet = new HashSet<Listing>();
        List<Listing> filteredList = new ArrayList<Listing>();
        try {            
            if (listings != null && listings.size() > 0) {
                if (amenities != null && amenities.size() > 0) {                    
                    for (Listing listing : listings) {
                        for (Amenity prop : amenities) {                            
                            if (listing.getAmenities() != null &&
                                listing.getAmenities().contains(prop)) {
                                filteredSet.add(listing);                                
                            }
                        }
                    }
                }
                else {
                    filteredSet.addAll(listings);
                }
            }
            filteredList.addAll(filteredSet);
            response.setResponse(filteredList);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response;
    }
    
    @Override
    public ServiceResponse<List<Listing>> filterByListingAttributes(List<Listing> listings, ListingFilter filter) {
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> filtered = new ArrayList<Listing>();
        try {            
            if (listings != null && listings.size() > 0) {
                if (filter != null) {                    
                    //CX: filter sqArea
                    this.filterListingSqArea(listings, filter.getSqAreaLow(), filter.getSqAreaHigh());
                    
                    //CX: filter rates
                    //this.filterListingDayRate(listings, filter.getDayRateLow(), filter.getDayRateHigh());
                    //this.filterListingWeekRate(listings, filter.getWeekRateLow(), filter.getWeekRateHigh());
                    this.filterListingMonthRate(listings, filter.getMonthRateLow(), filter.getMonthRateHigh());
                    //this.filterListingYearRate(listings, filter.getYearRateLow(), filter.getYearRateHigh());                    
                    this.filterListingShortDayRate(listings, filter.getShortDayRateLow(), filter.getShortDayRateHigh());
                    this.filterListingLongMonthRate(listings, filter.getLongMonthRateLow(), filter.getLongMonthRateHigh());
                    
                    //CX: filter rooms
                    this.filterListingNumBedrooms(listings, filter.getNumBedroomsLow(), filter.getNumBedroomsHigh());
                    this.filterListingNumBathrooms(listings, filter.getNumBathroomsLow(), filter.getNumBathroomsHigh());
                    
                    //CX: filter availablility
                    this.filterListingAvailable(listings, filter.isAvailable());
                    
                    filtered.addAll(listings);
                }
                else {
                    filtered.addAll(listings);
                }
            }
            response.setResponse(filtered);
            response.setSuccessful(true);            
        } 
        catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
        
    private boolean listingHasPlace(Listing listing, Place place) {
        boolean hasPlace = false;
        try {
            for (Place listingPlace : listing.getPlaces()) {
                if (StringUtils.equals(listingPlace.getId(), place.getId())) {
                    hasPlace = true;
                    break;
                }
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        return hasPlace;
    }
    
    private void filterListingSqArea(List<Listing> listings, double sqAreaLow, double sqAreaHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if (listing.getSqArea() >= sqAreaLow && listing.getSqArea() <= sqAreaHigh) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void filterListingDayRate(List<Listing> listings, String dayRateLow, String dayRateHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if ( !StringUtils.isEmpty(listing.getDayRate()) &&
                    RateUtils.toDouble(listing.getDayRate()) >= RateUtils.toDouble(dayRateLow) && 
                    RateUtils.toDouble(listing.getDayRate()) <= RateUtils.toDouble(dayRateHigh)) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void filterListingWeekRate(List<Listing> listings, String weekRateLow, String weekRateHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if ( !StringUtils.isEmpty(listing.getWeekRate()) &&
                    RateUtils.toDouble(listing.getWeekRate()) >= RateUtils.toDouble(weekRateLow) && 
                    RateUtils.toDouble(listing.getWeekRate()) <= RateUtils.toDouble(weekRateHigh)) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void filterListingMonthRate(List<Listing> listings, String monthRateLow, String monthRateHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if ( !StringUtils.isEmpty(listing.getMonthRate()) &&
                    RateUtils.toDouble(listing.getMonthRate()) >= RateUtils.toDouble(monthRateLow) && 
                    RateUtils.toDouble(listing.getMonthRate()) <= RateUtils.toDouble(monthRateHigh)) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void filterListingYearRate(List<Listing> listings, String yearRateLow, String yearRateHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if ( !StringUtils.isEmpty(listing.getYearRate()) &&
                    RateUtils.toDouble(listing.getYearRate()) >= RateUtils.toDouble(yearRateLow) && 
                    RateUtils.toDouble(listing.getYearRate()) <= RateUtils.toDouble(yearRateHigh)) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void filterListingShortDayRate(List<Listing> listings, String shortDayRateLow, String shortDayRateHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if (StringUtils.isEmpty(listing.getShortDayRate())) {
                    listing.setShortDayRate("0.0");
                }
                if ( !StringUtils.isEmpty(listing.getShortDayRate()) &&
                    RateUtils.toDouble(listing.getShortDayRate()) >= RateUtils.toDouble(shortDayRateLow) && 
                    RateUtils.toDouble(listing.getShortDayRate()) <= RateUtils.toDouble(shortDayRateHigh)) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void filterListingLongMonthRate(List<Listing> listings, String longMonthRateLow, String longMonthRateHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if ( !StringUtils.isEmpty(listing.getLongMonthRate()) &&
                    RateUtils.toDouble(listing.getLongMonthRate()) >= RateUtils.toDouble(longMonthRateLow) && 
                    RateUtils.toDouble(listing.getLongMonthRate()) <= RateUtils.toDouble(longMonthRateHigh)) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
        
    private void filterListingNumBedrooms(List<Listing> listings, int numBedroomsLow, int numBedroomsHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if (listing.getNumBedrooms() >= numBedroomsLow && listing.getNumBedrooms() <= numBedroomsHigh) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
        
    private void filterListingNumBathrooms(List<Listing> listings, int numBathroomsLow, int numBathroomsHigh) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if (listing.getNumBathrooms() >= numBathroomsLow && listing.getNumBathrooms() <= numBathroomsHigh) {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void filterListingAvailable(List<Listing> listings, boolean available) {
        List<Listing> filtered = new ArrayList();
        try {
            for (Listing listing : listings) {
                if (available) {
                    if (listing.isAvailable()) {
                        filtered.add(listing);
                    }
                }
                else {
                    filtered.add(listing);
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void geoFilterListings(List<Listing> listings, LatLng centerPoint, double maxKM) {
        List<Listing> filtered = new ArrayList();
        try {
            LatLng listingPoint = null;
            for (Listing listing : listings) {
                
                if (!StringUtils.isEmpty(listing.getLat()) && !StringUtils.isEmpty(listing.getLng()) ) {
                    listingPoint = new LatLng(Double.parseDouble(listing.getLat()), Double.parseDouble(listing.getLng()));
                    double distance = LatLngTool.distance(centerPoint, listingPoint, LengthUnit.KILOMETER);
                    
                    if (distance <= maxKM) {
                        filtered.add(listing);
                    }
                }
            }
            listings.clear();
            listings.addAll(filtered);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private Listing cloneListing(Listing listing) {
        Listing clone = cloner.deepClone(listing);
        //CX: hack
        clone.setPlaces( new ArrayList() );
        return clone;
    }
    
    private List<Listing> cloneListings(List<Listing> listings) {
        List<Listing> cloneList = new ArrayList<Listing>(0);
        for (Listing listing : listings) {
            cloneList.add(cloneListing(listing));
        }
        return cloneList;
    }
    
    private List<Listing> trimListingsData(List<Listing> listings) {
        List<Listing> trimmedListings = new ArrayList<Listing>();
        if (listings != null && listings.size() > 0) {
            
            for (Listing listing : listings) {
                Listing trimmed = new Listing();
                trimmed.setId(listing.getId());
                trimmed.setTitle(listing.getTitle());
                trimmed.setDisplayTitle(listing.getDisplayTitle());
                trimmed.setUrlTitle(listing.getUrlTitle());
                trimmed.setAddress(listing.getAddress());
                trimmed.setPrimaryPhoto(listing.getPrimaryPhoto());
                trimmed.setSqArea(listing.getSqArea());
                trimmed.setLongMonthRate(listing.getLongMonthRate());
                trimmed.setShortDayRate(listing.getShortDayRate());
                trimmed.setShortWeekRate(listing.getShortWeekRate());
                trimmed.setShortMonthRate(listing.getShortMonthRate());
                trimmed.setAvailable(listing.isAvailable());
                trimmed.setLeaseLongTerm(listing.isLeaseLongTerm());
                trimmed.setLeaseShortTerm(listing.isLeaseShortTerm());
                trimmed.setCommunity(listing.getCommunity());
                trimmed.setCity(listing.getCity());
                trimmedListings.add(trimmed);
            }
        }
        return trimmedListings;
    }
    
    /*
    @Override
    public ServiceResponse<List<Listing>> searchPostings(String searchTerm, SearchFilter searchFilter) {        
        ServiceResponse<List<Listing>> response = new ServiceResponse<List<Listing>>();
        List<Listing> postings = new ArrayList<Listing>();
        
        try {
            Set<Listing> postingSet = new HashSet<Listing>();
            
            String keywordTerm = searchFilter.getKeywordTerm();
            String placeTerm = searchFilter.getPlaceTerm();
            
            ServiceResponse<List<Listing>> rs1 = listingService.readAll(0, 0);
            
            if (placeTerm != null && !StringUtils.isEmpty(placeTerm)) {
                ServiceResponse<List<Place>> response1 = placeSearchService.searchPlaces(placeTerm);
                
                if (response1.isSuccessful() && response1.getResponse() != null && response1.getResponse().size() > 0) {
                    List<Place> places = response1.getResponse();                    
                    
                    for (Place place : places) {
                        ServiceResponse<List<Listing>> response2 = listingService.getPlacePostings(place.getId());
                        
                        if (response2.isSuccessful() && response2.getResponse() != null && response2.getResponse().size() > 0) {
                            postingSet.addAll(response2.getResponse());
                        }
                    }
                }
                
                postings.clear();
                postings.addAll( postingSet );
            }
            
            
            
            if (keywordTerm != null && !StringUtils.isEmpty(keywordTerm.trim())) {
                postings = this.search(keywordTerm, new ArrayList(postings));                
            }
            
            if (postings != null && postings.size() > 0 && searchFilter != null) {
                filterPrice(postings, searchFilter);
                filterSqArea(postings, searchFilter);
                filterNumBedroom(postings, searchFilter);
                filterNumBathroom(postings, searchFilter);
                //filterRentSale(postings, searchFilter);
                //filterFurnish(postings, searchFilter);
            }
            
            response.setResponse(postings);
            response.setSuccessful(true);            
        } catch (Exception ex) {
            response.setResponse(null);
            response.setSuccessful(false);
            response.setMessage(ex.getMessage());
            response.setException(ex);
            ex.printStackTrace();            
        }
        return response; 
    }
    
    private void filterPrice(List<Listing> postings, SearchFilter searchFilter) {
        try {
            List<Listing> filtered = new ArrayList<Listing>();
            double priceL = StringUtils.isEmpty(searchFilter.getPriceL()) ? 0 : Double.parseDouble(searchFilter.getPriceL());
            double priceU = StringUtils.isEmpty(searchFilter.getPriceU()) ? 999999999 : Double.parseDouble(searchFilter.getPriceU());

            if (priceL != 0 && priceU != 999999999) {
                for (Listing posting : postings) {
                    double price = StringUtils.isEmpty(posting.getPrice()) ? 0 : Double.parseDouble(posting.getPrice());
                    if (price >= priceL && price <= priceU) {
                        filtered.add(posting);
                    }
                }

                postings.clear();
                postings.addAll(filtered);
            }            
        } catch (Exception ex) {
            ex.printStackTrace();
        }      
    }
    
    private void filterSqArea(List<Listing> postings, SearchFilter searchFilter) {
        try {
            List<Listing> filtered = new ArrayList<Listing>();
            double sqL = StringUtils.isEmpty(searchFilter.getSqAreaL()) ? 0 : Double.parseDouble(searchFilter.getSqAreaL());
            double sqU = StringUtils.isEmpty(searchFilter.getSqAreaU()) ? 999999999 : Double.parseDouble(searchFilter.getSqAreaU());

            if (sqL != 0 && sqU != 999999999) {
                for (Listing posting : postings) {
                    double price = StringUtils.isEmpty(posting.getSqArea()) ? 0 : Double.parseDouble(posting.getSqArea());
                    if (price >= sqL && price <= sqU) {
                        filtered.add(posting);
                    }
                }
                postings.clear();
                postings.addAll(filtered);
            }               
        } catch (Exception ex) {
            ex.printStackTrace();
        }      
    }
    
    private void filterNumBedroom(List<Listing> postings, SearchFilter searchFilter) {
        try {
            List<Listing> filtered = new ArrayList<Listing>();
            int brL = searchFilter.getBedroomL();
            int brU = searchFilter.getBedroomU() == 0 ? 999999 : searchFilter.getBedroomU();

            if (brL != 0 || brU != 0) {
                for (Listing posting : postings) {
                    int numBedroom = posting.getNumBedrooms();
                    if (numBedroom >= brL && numBedroom <= brU) {
                        filtered.add(posting);
                    }
                }
                postings.clear();
                postings.addAll(filtered);
            }               
        } catch (Exception ex) {
            ex.printStackTrace();
        }      
    }
    
    private void filterNumBathroom(List<Listing> postings, SearchFilter searchFilter) {
        try {
            List<Listing> filtered = new ArrayList<Listing>();
            int bhL = searchFilter.getBathroomL();
            int bhU = searchFilter.getBathroomU() == 0 ? 999999 : searchFilter.getBathroomU();

            if (bhL != 0 || bhU != 0) {
                for (Listing posting : postings) {
                    int numBathroom = posting.getNumBathrooms();
                    if (numBathroom >= bhL && numBathroom <= bhU) {
                        filtered.add(posting);
                    }
                }
                postings.clear();
                postings.addAll(filtered);
            }               
        } catch (Exception ex) {
            ex.printStackTrace();
        }      
    }
            
    public List<Listing> search(String searchTerm) {
        List<Listing> response = MockDS.getPostings();
        List<Listing> results = new ArrayList<Listing>();
        if (response != null && response.size() > 0) {
            results = search (searchTerm, response);
        }
        return results;
    }

    public List<Listing> search(String searchTerm, List<Listing> postings) {
        List<Listing> results = new ArrayList<Listing>();
        
        try {            
            Directory index = new RAMDirectory();
            Analyzer analyzer = new EnglishAnalyzer(Version.LUCENE_36);

            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_36, analyzer);
            
            IndexWriter indexWriter = new IndexWriter(index, config);
            buildDocuments(indexWriter, postings);
            
            String[] arrFields = {"title", "desc", "addr"};
            Query q = new MultiFieldQueryParser(Version.LUCENE_36, arrFields, analyzer).parse(searchTerm);

            int hitsPerPage = getItemsLength(postings);
            IndexSearcher searcher = new IndexSearcher(index, true);
            TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage, true);
            searcher.search(q, collector);
            ScoreDoc[] hits = collector.topDocs().scoreDocs;

            System.out.println("Found " + hits.length + " hits.");
            for (int i = 0; i < hits.length; ++i) {
                
                int docId = hits[i].doc;
                Document d = searcher.doc(docId);
                System.out.println((i + 1) + ". " + d.get("title") + ". " + d.get("id"));
                
                for (Listing record : postings) {
                    if (StringUtils.equalsIgnoreCase(record.getId(), d.get("id"))) {
                        results.add(record);                    
                    }
                } 
            }
            searcher.close();        
        } 
        catch (Exception ex) {
            ex.printStackTrace();
        }        
        return results;
    }
    
    private void buildDocuments(IndexWriter writer, List<Listing> postings) throws IOException {
        if (postings != null && postings.size() > 0) {
            for (Listing project : postings) {
                addDoc(writer, project);
            }
            writer.commit();
        }        
    }
    
    
    private void addDoc(IndexWriter writer, Listing posting) throws IOException {
        Document doc = new Document();
        doc.add(new Field("id", posting.getId(), Field.Store.YES, Field.Index.NOT_ANALYZED));
        doc.add(new Field("title", posting.getTitle(), Field.Store.YES, Field.Index.ANALYZED));
        doc.add(new Field("desc", posting.getDescription(), Field.Store.YES, Field.Index.ANALYZED));
        doc.add(new Field("addr", posting.getAddress(), Field.Store.YES, Field.Index.ANALYZED));
                
        writer.addDocument(doc);
    }
    
    private int getItemsLength(List<Listing> posting) {
        int length = 0;
        if (posting != null && posting.size() > 0) {
            length = posting.size();
        }
        return length;
    }
    */

    @Override
    public ServiceResponse<SearchResult> searchListings4(SearchOption searchOption) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }






}
